import * as THREE from "three"
import configConst from "../constant/config.const"
import CameraData from "../data/CameraData"
import FloorsViewData from "../data/FloorsViewData"
import ViewmodeData from "../data/ViewmodeData"
import measurementEnum from "../enum/measurement.enum"
import unitTypeEnum from "../enum/unitType.enum"
import P from "../math/27687"
import y from "../math/7170"
import geoFT from "../math/geoFT"
import AnimatedNumber from "../object/AnimatedNumber"
import Logger from "../utils/Logger"

interface makeData {
  start_position: THREE.Vector3
  end_position: THREE.Vector3
  visible: boolean
  floorId: string
  roomId: string
  type: number
  text: string
}
interface previousDerivedData {
  sid: string
  rotation: number
  labelVisible: boolean
  visible: boolean
  length: number
  displayLength: string
  labelContents: string
  opacity: AnimatedNumber
}
const b = new THREE.Vector2()
const M = new THREE.Vector3()
const x = new THREE.Vector2()
const I = new THREE.Vector2(),
  R = (e, t, i) => {
    const s = ((e, t, i) => (
      P.q9(i, e, x),
      P.q9(i, t, I),
      {
        pixelDistance: x.distanceTo(I),
        startScreenPosition: x,
        endScreenPosition: I
      }
    ))(e, t, i)
    return {
      screenPosition: ((e, t, i) => (M.copy(e).add(t).multiplyScalar(0.5), P.q9(i, M, b), b))(e, t, i),
      rotation: C(x, I),
      pixelDistance: s.pixelDistance,
      startScreenPosition: s.startScreenPosition,
      endScreenPosition: s.endScreenPosition
    }
  },
  C = (e, t) => {
    const i = e.y - t.y,
      s = e.x - t.x
    let n = Math.atan2(i, s) * y.MN
    return (n = n >= 90 || n <= -90 ? n + 180 : n), n
  }
const L = new Logger("line-data")
export default class LineDerivedDataFactory {
  cameraData: CameraData
  viewmodeData: ViewmodeData
  floorsViewData: FloorsViewData
  isCurrentSweepAligned: () => boolean
  getUnits: () => string
  isFeatureEnabled: () => boolean
  visibleFilter: (e: any) => { lineVisibleByFeatureType: boolean; labelVisibleByFeatureType: boolean }
  derivedDataCache: Record<string, { getLineData: () => makeData; previousDerivedData: previousDerivedData }>
  dollhouseLineStyle: number
  setVisibilityFilter: (e: any) => void
  defaultVisibilityFilter: () => void
  make: (e: string, t: () => makeData, i: previousDerivedData) => any
  tmpVec: THREE.Vector2
  update: (e: any) => any
  get: (e: string) => previousDerivedData
  remove: (e: any) => void
  clear: () => void
  visibleByFloorAndModes: (e: string, a: number) => boolean
  constructor(
    e,
    t,
    i,
    s,
    n,
    r,
    o = () => ({
      lineVisibleByFeatureType: !0,
      labelVisibleByFeatureType: !0
    })
  ) {
    this.cameraData = e
    this.viewmodeData = t
    this.floorsViewData = i
    this.isCurrentSweepAligned = s
    this.getUnits = n
    this.isFeatureEnabled = r
    this.visibleFilter = o
    this.derivedDataCache = {}
    this.dollhouseLineStyle = measurementEnum.ThreeD
    this.setVisibilityFilter = e => {
      this.visibleFilter = e
    }
    this.defaultVisibilityFilter = () => {
      this.visibleFilter = () => ({
        lineVisibleByFeatureType: !0,
        labelVisibleByFeatureType: !0
      })
    }
    this.make = (e, t, i) => {
      const s = t(),
        { start_position, end_position, visible, floorId, type, text } = s,
        d = void 0 !== i ? i.opacity.value : 0,
        c = visible && this.isFeatureEnabled(),
        u = c && this.visibleByFloorAndModes(floorId, type),
        p = 0 === d || (d === configConst.labelConfig.LABEL_HIDDEN_OPACITY && u)
      if (i && p && (!c || !u)) return i
      let g = u
      let m = u
      if (u) {
        const t = this.visibleFilter(e)
        g = g && t.lineVisibleByFeatureType
        m = m && t.labelVisibleByFeatureType
      }
      let w = 0
      const f = start_position.distanceTo(end_position)
      const T = ((e: number, t = unitTypeEnum.IMPERIAL) => {
        let i = '0"'
        switch (t) {
          case unitTypeEnum.IMPERIAL:
            const { feet: t, inches: s } = geoFT.o(e)
            i = t > 0 ? `${t}' ${s}"` : `${s}"`
            break
          case unitTypeEnum.METRIC:
            i = `${e.toFixed(2)}m`
        }
        return i
      })(f, this.getUnits())
      if (m || !p) {
        const { width, height } = ((e, t = 10, i = 40) => ({
            width: Math.max(9 * Math.max(e, 2) + t, i),
            height: 18 + t
          }))(text.length + T.length),
          i = R(start_position, end_position, this.cameraData)
        this.tmpVec.copy(i.startScreenPosition).sub(i.endScreenPosition)
        let s = Math.abs(this.tmpVec.y) < Math.abs(this.tmpVec.x) ? height : width
        configConst.labelConfig.ALIGN_LABELS && ((s = width), (w = i.rotation)), (m = m && i.pixelDistance > s)
      }
      let y: AnimatedNumber
      const P = g && m ? 1 : g && !m ? configConst.labelConfig.LABEL_HIDDEN_OPACITY : 0
      i
        ? ((y = i.opacity),
          (y.endValue === P && g === i.visible && m === i.labelVisible) || y.modifyAnimation(y.value, P, configConst.labelConfig.FADE_DURATION))
        : (y = new AnimatedNumber(0, P, configConst.labelConfig.FADE_DURATION))
      const b: previousDerivedData = {
        sid: e,
        rotation: w,
        labelVisible: m,
        visible: g,
        length: f,
        displayLength: T,
        labelContents: text.length > 0 ? `${T} ${text}` : T,
        opacity: y
      }
      this.derivedDataCache[e] = {
        getLineData: t,
        previousDerivedData: b
      }
      return b
    }
    this.update = (e: string) => {
      if (this.derivedDataCache[e]) {
        const { getLineData, previousDerivedData } = this.derivedDataCache[e]
        return this.make(e, getLineData, previousDerivedData)
      }
      L.warn(`data not found for ${e}`)
    }
    this.get = (e: string) => {
      if (this.derivedDataCache[e]) return this.derivedDataCache[e].previousDerivedData
    }
    this.remove = (e: string) => {
      this.derivedDataCache[e] && delete this.derivedDataCache[e]
    }
    this.clear = () => {
      this.derivedDataCache = {}
    }
    this.visibleByFloorAndModes = (e, t) => {
      if (this.floorsViewData.transition.progress.active) return !1
      const i = this.viewmodeData.isInside(),
        s = this.viewmodeData.isFloorplan(),
        n = this.viewmodeData.isDollhouse(),
        r = this.floorsViewData.currentFloor,
        o = !r,
        a = !(!r || r.id !== e),
        h = e === this.floorsViewData.topFloorId,
        l = t === measurementEnum.FloorplanOnly && s && (a || (h && o)),
        d = t === this.dollhouseLineStyle && n && (a || o)
      return (t === measurementEnum.ThreeD && i && this.isCurrentSweepAligned()) || d || l
    }
    this.tmpVec = new THREE.Vector2()
  }
  setDollhouseLineStyle(e: number) {
    this.dollhouseLineStyle = e
  }
}
